package org.dei.perla.sys.device.fpcfactory;

import java.util.ArrayList;

import org.dei.perla.component.Component;
import org.dei.perla.utils.logger.LogRecord;
import org.dei.perla.utils.messages.AdapterFpcMessage;
import org.dei.perla.utils.messages.MsgUtility;
import org.dei.perla.sys.device.adapter.AdapterServerConcrete;
import org.dei.perla.sys.device.fpc.FPC;
import org.dei.perla.sys.device.fpc.SampleFPCTypeA;
import org.dei.perla.sys.device.fpc.SampleFPCTypeB;
import org.dei.perla.utils.pipe.Pipe;
import org.dei.perla.utils.waiter.Waitable;
import org.dei.perla.utils.waiter.Waiter;

/**
 * FPC Factory temporaneo
 *
 */
public class FpcFactory extends Component {
	
	/**
	 * Nome del componente
	 */
	protected static final String name = "FPC Factory";
	
	/**
	 * Pipe sui quali vengono ricevuti dati dal server
	 */
	private Pipe<AdapterFpcMessage> pPipeIn;
	/**
	 * Pipe sulla quale vengono scritti i dati per il server
	 */
	private Pipe<AdapterFpcMessage> pPipeOut;
	
	/**
	 * Waiter, riceve i messagi in arrivo sulle pipe
	 */
	private Waiter pFactWaiter;
	
	private ArrayList<FPC> pFpcList;
	
	/**
	 * Event listener
	 */
	private Thread pEventListener;
	
	/**
	 * Costruttore
	 *
	 */
	public FpcFactory (AdapterServerConcrete parAdapter) {
		
		super(name);
		
		this.pPipeIn = parAdapter.getFactPipeOut();
		this.pPipeOut = parAdapter.getFactPipeIn();
		
		this.pFpcList = new ArrayList<FPC>();
	
		this.pFactWaiter = new Waiter();
		
		// Attacco la pipe di input al waiter
		this.pFactWaiter.addWaitable(this.pPipeIn.getWaitable());
	}
	
	/**
	 * Controlla tutte le pipe e risveglia i metodi relativi alle pipe
	 * su cui sono arrivati i messaggi
	 */
	public void start(){
		
		this.pFactWaiter.start();
		
		//Avvia il thread di ricezione
		this.pEventListener = new Thread() {
			public void run() {
				pReceive();
			}
		};
		this.pEventListener.start();
		
		this.loggerNotify(LogRecord.Type.Information, "Factory started!", LogRecord.Verbosity.Medium);
	}
	
	/**
	 * Ferma l'esecuzione del componente
	 */
	public void stop(){
		
		this.setStarted(false);
		
		this.pFactWaiter.stop();
		try {
			this.pEventListener.join();
		}catch(Exception parException) { }
		
		this.pEventListener = null;
		
		this.loggerNotify(LogRecord.Type.Information, "Factory stopped!", LogRecord.Verbosity.Medium);
	}
	
	/**
	 * Si mette in ricezione sulle pipe
	 */
	public void pReceive(){
		
		Waitable tWaitable = null;
		
		do{
			
			tWaitable = this.pFactWaiter.waitNext();
			
			if (tWaitable != null){
				
				// Ottieni la pipe che ha scatenato l'evento
				@SuppressWarnings("unused")
				Pipe pipe = (Pipe) tWaitable.getParentComponent();
				
				this.receiveManager();
			}
			
		} while(tWaitable != null);
	}

	/**
	 * Controlla la pipe e risveglia la factory
	 */
	public void receiveManager(){
		
		AdapterFpcMessage tFrame = (AdapterFpcMessage) pPipeIn.dequeue();
		
		// Arriva una richiesta di binding --> crea FPC e rispondi un msg di ack
		if (tFrame.getFlag() == MsgUtility.flagBinding) {
			
			// Gestione del file descriptor di esempio
			// Solo per vedere che può generare diversi tipi di fpc
			String XMLdescriptor = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
				"<deviceType>1</deviceType>\n";
			
			String XMLdescriptor2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" +
				"<deviceType>2</deviceType>\n";
			
			byte[] device1 = XMLdescriptor.getBytes();
			byte[] device2 = XMLdescriptor2.getBytes();
			
			byte[] tPayload = tFrame.getPayload();
			
			byte[] tContent = MsgUtility.getNormalMsgContent(tPayload);
			
			this.loggerNotify(LogRecord.Type.Information, "Binding message received",
					"Device descriptor: " + new String(tContent), LogRecord.Verbosity.Medium);
			
			if(pIsEqual(tContent, device1)){
				
				this.makeFpc("Type A", tFrame, tFrame.getPipeOut(), tFrame.getPipeIn(), 1 );
			}
			else if(pIsEqual(tContent, device2)){

				this.makeFpc("Type B", tFrame, tFrame.getPipeOut(), tFrame.getPipeIn(), 2 );
			}
		}
		
		else {
			// todo: altre azioni diverse dalla creazione di una nuova FPC
		}
		
	}
	
	private boolean pIsEqual(byte[] array1, byte[] array2){
		
		if(array1.length != array2.length) return false;
		for(int i = 0; i < array1.length; i++){
			
			if(array1[i] != array2[i])
				return false;
		}
		
		return true;
	}
	
	/**
	 * Genera una nuova FPC
	 * 
	 * @param parDesc, descrizione(futuro XML)
	 * @param parPipeIn, pipe di ricezione
	 * @param parPipeOut, pipe di scrittura
	 * @return true se crea fpc senza problemi
	 */
	@SuppressWarnings("unchecked")
	private boolean makeFpc (String parDesc, AdapterFpcMessage parMsg, Pipe parPipeIn, Pipe parPipeOut, int parDeviceType) {
		
		this.loggerNotify(LogRecord.Type.Information, "Creating FPC...", LogRecord.Verbosity.Medium);
		
		if(parDeviceType == 1){
			
			SampleFPCTypeA tTemp = new SampleFPCTypeA(parDesc, parMsg, parPipeIn, parPipeOut);
			this.pFpcList.add(tTemp);
			tTemp.loggerAttach(this);
			tTemp.start();
			
		}
		else if(parDeviceType == 2){
			
			SampleFPCTypeB tTemp = new SampleFPCTypeB(parDesc, parMsg, parPipeIn, parPipeOut);
			this.pFpcList.add(tTemp);
			tTemp.loggerAttach(this);
			tTemp.start();
			
		}
		return true;
	}

	/**
	 *  Ritorna il waiter associato
	 *
	 * @return Il waiter associato
	 */
	public Waiter getFactWaiter() {
		return this.pFactWaiter;
	}
	
}